
import {floorMod} from "../util/math.js";
// import * as d3 from "../lib/d3.js";
import * as d3 from "d3";
import * as twod from "../canvas/twod.js";
import {$} from "../util/seq.js";

const MAX_TASK_TIME = 40;   // amount of time before a task yields control (millis)
const MIN_SLEEP_TIME = 10;  // amount of time a task waits before resuming (millis)

// CONSIDER: Interleave grid points to improve UX of very dense grids, to avoid them filling slowly from top-to-bottom
// UNDONE: make this its own component that is sensitive to show_grid_points changes
function drawGridPoints(ctx, viewboxAgent, globeAgent, layerAgent, cancel) {
    const globe = globeAgent.value();
    const layer = layerAgent.value();
    if (!globe || !layer) {
        return;
    }

    ctx.fillStyle = "rgba(255, 255, 255, 0.75)";
    const {width, height} = viewboxAgent.value();
    const stream = globe.projection.stream({
        point(x, y) {
            if (0 <= x && x < width && 0 <= y && y < height) {
                // fillRect is processed at the end of the event turn, but it alpha blends, as opposed to setting
                // the pixel directly.
                ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
            }
        },
    });
    const grid = layer.grid();
    const {isDefined} = layer.field();
    let i = 0;

    // Draw grid points in batches.
    (function work() {
        if (!cancel.requested) {
            const end = Date.now() + MAX_TASK_TIME;
            i = grid.forEach((λ, φ, i) => {
                if (isDefined(i)) {
                    λ = floorMod(180 + λ, 360) - 180;  // some projections don't map to [-180, 180)
                    stream.point(λ, φ);
                }
                return Date.now() > end;
            }, i);
            if (i === i) {
                setTimeout(work, MIN_SLEEP_TIME);
            }
        }
    })();
}

function changeAlpha(data, alpha) {
    for (let i = 3; i < data.length; i += 4) {
        if (data[i] !== 0) {
            data[i] = alpha;
        }
    }
}

export function drawOverlay(
    overlayGridAgent,
    viewboxAgent,
    globeAgent,
    rendererAgent,
    activeLayerAgent,
    fieldAgent,
    overlay_type,
    animation_enabled,
    show_grid_points,
) {
    const field = fieldAgent.value();
    if (!field || !rendererAgent.value()) {
        return;
    }

    const ctx = d3.select("#overlay").node().getContext("2d");
    const activeLayer = activeLayerAgent.value();
    const {overlay} = field;

    twod.clearContext(ctx);

    if ((overlay_type ?? "none") !== "none") {

        if (overlay !== undefined) {
            const alpha = animation_enabled ? activeLayer.alpha.animated : activeLayer.alpha.single;
            changeAlpha(overlay.data, alpha);
            ctx.putImageData(overlay, 0, 0);
        }

        if (show_grid_points) {
            overlayGridAgent.submit(function() {
                return drawGridPoints(ctx, viewboxAgent, globeAgent, activeLayerAgent, this.cancel);
            });
        } else {
            overlayGridAgent.cancel();
        }
    }
}

export function defineOverlayDrawer(
    overlayAgent,
    overlayGridAgent,
    viewboxAgent,
    rendererAgent,
    activeLayerAgent,
    globeAgent,
    fieldAgent,
    sharedStore,
) {
    function submitDrawOverlay(overlay_type, animation_enabled) {
        overlayAgent.submit(() => drawOverlay(
            overlayGridAgent,
            viewboxAgent,
            globeAgent,
            rendererAgent,
            activeLayerAgent,
            fieldAgent,
            overlay_type,
            animation_enabled,
            sharedStore.get("show_grid_points"),
        ));
    }

    fieldAgent.on($`update`, () => {
        submitDrawOverlay(sharedStore.get("overlay_type"), sharedStore.get("animation_enabled"));
    });
    sharedStore.on($`change:overlay_type change:animation_enabled change:show_grid_points`, () => {
        submitDrawOverlay(sharedStore.get("overlay_type"), sharedStore.get("animation_enabled"));
    });

    rendererAgent.on($`start`, () => {
        // Immediately clear overlay. Chrome will wait until mouse pauses before agent is triggered. :(
        twod.clearCanvas(d3.select("#overlay").node());
        submitDrawOverlay(null, null);
    });
    rendererAgent.on($`start redraw`, () => overlayGridAgent.cancel());

}
